/*
 * Copyright (c) 2003-2010, Live Media Pty. Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice, this list of
 *     conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice, this list of
 *     conditions and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *  3. Neither the name of Live Media nor the names of its contributors may be used to endorse
 *     or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.colony.networkvm;

import java.io.IOException;
import java.util.HashMap;

import com.argot.TypeException;
import com.argot.TypeLibrary;
import com.argot.TypeMap;
import com.argot.TypeMapper;
import com.argot.TypeMapperCore;
import com.argot.TypeMapperDynamic;
import com.argot.TypeMapperError;
import com.argot.meta.DictionaryRelation;
import com.argot.network.DynamicClientTypeMapper;
import com.argot.network.TypeClient;
import com.argot.remote.MetaInterface;
import com.argot.remote.MetaLocation;
import com.argot.remote.MetaMethod;
import com.argot.remote.MetaObject;
import com.colony.ColonyEnvironment;
import com.colony.ColonyException;
import com.colony.IColonyClient;
import com.colony.IContainer;
import com.colony.IProxyFactory;
import com.colony.channel.ChannelClient;
import com.colony.channel.ChannelLoader;
import com.colony.concurrent.SystemThreadPool;
import com.colony.networkvm.machine.NvmInstance;
import com.colony.networkvm.machine.NvmState;
import com.colony.networkvm.proxy.NvmMetaStubFactory;
import com.colony.remote.RemoteException;
import com.colony.remote.rpc.RpcMetaClient;
import com.colony.remote.rpc.RpcMetaMethodRequest;
import com.colony.remote.rpc.RpcMetaStubFactory;
import com.colony.transport.Client;

public class NvmClient 
implements IColonyClient
{
	private boolean _isBound;
	private HashMap _factoryMap;
	private Client _transport;
	private Client _channel;
	private NvmDelegateFactory _delegateFactory;
	
	private RpcMetaClient _rpcMetaClient;
	private MetaLocation _baseLocation;
	private MetaMethod _processMethod;
	
	// Bound variables.
	private String _hostName;
	private ColonyEnvironment _environment;
	private TypeLibrary _library;
	private NvmEnvironment _nvmEnvironment;
	
	private INvmHost _host;

	public NvmClient( Client transport, Client channel, NvmDelegateFactory delegateFactory )
	{
		_factoryMap = new HashMap();
		_isBound = false;
		_transport = transport;
		_channel = channel;
		_delegateFactory = delegateFactory;
		
		_environment = null;
		_library = null;
	}

	public IContainer getHostContainer()
	{
		return _environment.getHostContainer();
	}

	public TypeLibrary getTypeLibrary()
	{
		return _library;
	}

	public NvmDelegateFactory getDelegateFactory()
	{
		return _delegateFactory;
	}
	
	public Object getFront( MetaLocation location ) 
	throws RemoteException, TypeException
	{
		MetaObject metaObject = _host.getObject(location);
		Class clss = _library.getClass(metaObject.getType());
		return getFactory(clss).getProxy(location);
	}

	public Object getFront( MetaObject metaObject ) 
	throws RemoteException, TypeException
	{
		Class clss = _library.getClass(metaObject.getType());
		return getFactory(clss).getProxy(metaObject.getLocation());
	}
	
	public String getHost()
	{
		return _hostName;
	}
	
	public IProxyFactory getFactory( Class clss )
	throws TypeException
	{
		IProxyFactory factory = (IProxyFactory) _factoryMap.get(clss);
		if (factory == null )
		{
			int[] ids = _library.getId(clss);
			if (ids.length>1 && ids.length==0)
				throw new TypeException("class bound to multiple types");
			
			MetaInterface metaInterface = (MetaInterface) _library.getStructure(ids[0]);
			factory = new NvmMetaStubFactory( _hostName, _nvmEnvironment, metaInterface, clss );
			setFactory( clss, factory );
		}
		return factory;
	}

	public void setFactory( Class clss, IProxyFactory factory )
	{
		_factoryMap.put(clss,factory);
	}

	public NvmInstance getNvmInstance()
	{
		return new NvmInstance(_nvmEnvironment, _library);
	}

	public void executeInstance( NvmInstance instance )
	throws RemoteException
	{
		instance.execute(instance);
	}

	public void sendToHost( NvmInstance instance )
	throws RemoteException
	{
		Object[] args = new Object[1];
		args[0] = instance.getState();
		RpcMetaMethodRequest methodRequest = new RpcMetaMethodRequest(_baseLocation,_processMethod,args);
		_rpcMetaClient.process(methodRequest);
		Throwable ex = methodRequest.getException();
		if (ex!=null)
		{
			instance.setException(ex);
		}
		else
		{
			instance.setState( (NvmState) methodRequest.getReturnValue() );
		}
	}

	public void bind( String hostName, ColonyEnvironment environment ) 
	throws RemoteException, TypeException, IOException
	{
		if ( _isBound ) throw new RemoteException( "Client already bound" );
		_isBound = true;
		
		_hostName = hostName;
		_environment = environment;
		_library = _environment.getTypeLibrary();
		_nvmEnvironment = new NvmEnvironment( _environment, _delegateFactory, _library );
		
		TypeClient typeClient = new TypeClient( _library, _transport );
		TypeMapper dynMapper = new TypeMapperCore( new DynamicClientTypeMapper(  typeClient ));
		TypeMap dynMap = new TypeMap( _library, dynMapper );
		dynMap.setReference(TypeMap.REFERENCE_MAP, dynMap);
		_rpcMetaClient = new RpcMetaClient( typeClient, dynMap );
		
		
		int nvmHostId = _library.getTypeId("nvm.host","1.3");
		DictionaryRelation nvmHostProcess = new DictionaryRelation(nvmHostId,"nvm.host.process");
		int methodId = _library.getTypeId(nvmHostProcess);
		_processMethod = (MetaMethod) _library.getStructure(methodId);
		
		//_processMethod = (MetaMethod) _library.getStructure(_library.getTypeId("nvm.host.process"));
		
		// get the base object.
		MetaObject baseObject = typeClient.getBaseObject( dynMap );
		_baseLocation = baseObject.getLocation();
		MetaInterface metaClass = (MetaInterface) _library.getStructure( baseObject.getType() );
		Class clss = _library.getClass( baseObject.getType() );
		RpcMetaStubFactory factory = new RpcMetaStubFactory(_rpcMetaClient, metaClass, clss);
		Object o = factory.getProxy( baseObject.getLocation() );

		// setup the channel client.

		if ( _channel != null )
		{
			ChannelClient channelClient = new ChannelClient( _channel, _library, SystemThreadPool.getThreadPool() );
			ChannelLoader.bindClientMap( dynMap, channelClient );
		}

		// add the host to our local environment.
		_host = (INvmHost) o;
		
	}

	public boolean isBound()
	{
		return _isBound;
	}

	public void disconnect() 
	throws ColonyException
	{
		throw new ColonyException("not implemented");
		
	}
	
}
